본문으로 건너뛰기

1장-함수를 이용한 추상화

1 - 1 프로그래밍의 기본 요소

강력한 프로그래밍 언어는 단순히 컴퓨터가 수행할 과제를 지시하는 수단 이상의 역할을 합니다. 프로그래밍 언어는 우리가 과정에 관한 생각들을 조직화하는 틀로 작용합니다. 따라서 프로그래밍 언어를 고찰할 때는 단순한 아이디어들을 조합해서 더 복잡한 아이디어를 만드는 데 사용하는 수단에 주의를 기울여야 합니다. 모든 강력한 언어는 이를 위해 다음 세 가지 메커니즘을 제공합니다:

  • 원시 표현식 (Primitive Expressions): 언어와 관련된 가장 단순한 개체를 나타냅니다.
  • 조합 수단 (Means of Combination): 단순한 요소들로부터 복합적인 요소를 만드는 데 사용됩니다.
  • 추상화 수단 (Means of Abstraction): 복합적인 요소들에 이름을 붙여서 하나의 단위로 다루는 데 사용됩니다.

프로그래밍에서 우리가 다루는 요소들은 크게 함수와 데이터로 나뉩니다. 데이터는 우리가 조작하고자 하는 재료이고, 함수는 데이터를 다루는 규칙들을 서술한 것입니다. 따라서 모든 강력한 프로그래밍 언어는 반드시 원시 데이터와 원시 함수를 서술하는 기능이 있어야 하고, 그런 함수들과 데이터를 조합하고 추상화하는 수단들도 제공해야 합니다.

이번 장에서는 함수를 구축하는 규칙들에 집중하기 위해 단순한 수치 데이터만 다루기로 합니다. 이후의 장들에서 보겠지만, 이번 장의 규칙들은 복합 데이터를 조작하는 함수를 구축하는 데에도 적용됩니다.

1.1.1 표현식

프로그래밍을 처음 배울 때 유용한 접근 방식 중 하나는 전형적인 상호작용 방식에 따라 자바스크립트 해석기를 직접 사용해 보는 것입니다. 사람과 해석기의 전형적인 상호작용 방식이란, 사람이 해석기의 프롬프트에서 하나의 문장을 입력하고, 해석기가 그 문장을 평가해서 그 결과를 화면에 표시하는 것입니다. 표현식 문장은 표현식과 세미콜론으로 구성됩니다. 표현식은 하나 이상의 원시 표현식으로 구성되는데, 여러 원시 표현식 중 하나로 수가 있습니다. 예를 들어 자바스크립트 해석기에 다음과 같은 코드를 입력하면 해석기는 다음과 같은 평가 결과를 출력합니다.

486; // 486

수를 나타내는 표현식들을 연산자(+, *)로 조합할 수 있습니다. 그 결과는 연산자들에 해당하는 원시 함수를 해당 수들에 적용하는 하나의 복합 표현식입니다. 다음은 그러한 복합 표현식들의 예입니다.

137 + 349; // 486

1000 - 334; // 666

5 * 99; // 495

10 / 4; // 2.5

2.7 + 10; // 12.7

이처럼 다른 표현식을 구성요소로 담고 있는 표현식을 가리켜 조합이라고 부릅니다. 연산자 조합의 값은 연산자로 지정된 함수를 인수들, 즉 피연산자 값들에 적용해서 구합니다. 연산자를 두 피연산자 사이에 배치하는 관례를 중위 표기법이라고 부릅니다. 중위 표기법은 학교와 일상생활에서 익숙한 수학 표기법과 동일합니다. 수학에서 수식 안에 수식을 중첩하는 것처럼 연산자 조합도 중첩할 수 있습니다. 즉, 연산자 조합 자체를 다른 연산자 조합의 피연산자로 사용할 수 있습니다.

자바스크립트에서도 연산 순서의 혼동을 피하기 위해 소괄호로 연산자 조합들을 묶을 수 있습니다. 소괄호를 생략하면 자바스크립트는 통상적인 관례에 따라 연산 순서를 결정합니다. 예를 들어 다음은 3 _ 5 + 10 / 2는 (3 _ 5) + (10 / 2)에 해당합니다. 이는 연산자 *와 /가 연산자 +와 -보다 우선순위가 높다는 것을 의미합니다.

일련의 덧셈과 뺄셈, 곱셈과 나눗셈도 마찬가지로 오른쪽에서 왼쪽으로 평가됩니다. 예를 들어 1 - 5 / 2 _ 4 + 3는 (1 - ((5 / 2) _ 4)) + 3에 해당합니다. 이를 연산자가 왼쪽 결합이라고 합니다.

표현식들의 중첩과 자바스크립트 해석기가 평가할 수 있는 표현식의 전반적인 복잡도에는 이론적으로 제한이 없습니다. 예를 들어, 3 _ 2 _ (3 - 5 + 4) + 27 / 6 * 10는 자바스크립트 해석기에 의해 쉽게 57로 평가될 수 있습니다.

복잡한 표현식 문장이 주어져도 해석기는 항상 동일한 기본 주기로 작동합니다. 해석기는 사용자가 입력한 문장을 읽고, 그 문장을 평가하고, 결과를 출력합니다. 이러한 주기를 반복하는 것을 **REPL (read-evaluate-print loop)**이라고 부릅니다. 자바스크립트 REPL에서 출력을 위해 특별한 명령이 필요하지 않음을 주목하십시오. 해석기는 주어진 표현식의 평가 결과를 자동으로 출력합니다.

1.1.2 이름 붙이기와 환경

계산적 객체에 이름을 붙여서 그 이름으로 객체를 지칭하는 수단은 프로그래밍 언어의 필수 기능입니다. 자바스크립트에서 상수에 이름을 붙이는 방법은 const 키워드를 사용하는 상수 선언입니다. 상수 선언을 통해 특정 값을 하나의 이름에 연관시킬 수 있습니다.

예를 들어, 다음 코드에서 해석기는 숫자 2를 size라는 이름에 연관시킵니다. 이제부터 size라는 이름은 값 2를 지칭합니다.

const size = 2;

size; // 2 5 * size; // 10

다음은 const를 사용하여 상수를 선언하고 이를 표현식에 사용하는 예입니다.

const pi = 3.14159; const radius = 10;

const area = pi _ radius _ radius; area; // 314.159

const circumference = 2 _ pi _ radius; circumference; // 62.8318

이처럼 복합적인 연산의 결과를 간단한 이름으로 지칭할 수 있다는 점에서 상수 선언은 가장 단순한 추상화 수단입니다. 예를 들어 circumference는 계산된 원의 둘레를 지칭합니다. 복잡한 구조의 계산적 객체를 사용할 때마다 그 구조를 반복해서 명시하는 것은 불편하므로, 상수 선언을 통해 복잡한 구조의 세부 사항을 기억하고 관리할 수 있습니다.

이름과 값을 연관시키고 이름으로부터 값을 조회하려면 해석기는 이름-객체 쌍을 저장하고 관리하는 특정한 메모리 공간을 필요로 합니다. 이 메모리 공간을 **환경(environment)**이라고 부릅니다. 하나의 계산에 다수의 환경이 관여할 수 있으며, 여기서 말하는 환경은 프로그래밍 환경을 의미합니다.

1.1.3 연산자 조합의 평가

이번 장의 목표 중 하나는 절차적 사고에 관한 논점들을 잘 구분하는 것입니다. 연산자 조합을 평가할 때 해석기가 따르는 절차는 다음과 같습니다:

  1. 조합의 피연산자 표현식들을 평가한다.
  2. 연산자가 나타내는 함수를 인수(피연산자들의 값)들에 적용한다.

이 규칙은 계산적인 과정에 관해 일반적으로 중요한 사항 몇 가지를 잘 보여줍니다. 첫째, 단계 1은 주어진 조합의 평가 과정을 완료하기 위해서는 먼저 조합의 각 피연산자를 평가해야 함을 말해줍니다. 이는 규칙의 한 단계에서 규칙 자신을 수행해야 함을 뜻합니다. 즉, 이 평가 규칙은 재귀적입니다.

재귀 덕분에 깊게 중첩된 조합의 평가 규칙도 아주 간결하게 표현할 수 있습니다. 예를 들어, 다음과 같은 복합 연산자 조합을 평가해봅시다.

(2 + 4 _ 6) _ (3 + 12);

이 복합 연산자 조합을 평가하려면 서로 다른 네 가지 조합에 평가 규칙을 적용해야 합니다. 그 과정을 하나의 트리 형태로 시각화할 수 있습니다. 트리의 각 노드는 하나의 조합을 나타내고, 노드에서 뻗어나온 갈래들은 그 조합의 연산자와 피연산자들로 이어집니다. 이러한 트리 표현에서, 조합의 평가 과정은 트리의 말단 노드들에서 출발해서 피연산자 값들을 해당 연산자에 따라 결합해서 점차 위쪽 노드들로 올려보내는 과정에 해당합니다. 일반적으로 재귀는 이처럼 트리 형태의 위계 구조로 조직화된 객체들을 다루는 데 대단히 강력한 기법입니다. 실제로, 이처럼 값들을 위로 올려보내는 형태의 평가 규칙은 트리 누산이라고 부르는 좀 더 일반적인 과정의 한 사례입니다.

단계 1을 재귀적으로 거듭 적용하다 보면 조합이 아니라 원시 표현식 (수치나 이름 같은)을 평가해야 하는 지점에 도달합니다. 원시 표현식 평가에는 다음과 같은 규칙들이 적용됩니다:

  • 수치의 값은 해당 숫자들이 나타내는 바로 그 값이다.
  • 이름의 값은 현재 환경에서 그 이름에 연관된 객체이다.

여기서 주목할 점은 표현식 안의 이름이 뜻하는 바를 환경이 결정한다는 점입니다. 자바스크립트 같은 대화식(상호작용적) 언어에서 x + 1 같은 표현식의 값을 이야기하려면 x라는 이름의 의미를 제공하는 환경에 관한 정보가 꼭 필요합니다. 평가가 일어나는 문맥을 제공하는 환경이라는 일반적인 개념은 우리가 프로그램의 실행을 이해할 때 중요한 역할을 합니다.

1.1.4 복합 함수

앞에서 우리는 자바스크립트 프로그래밍의 원시 요소들을 살펴보았습니다. 정리하자면 다음과 같습니다:

  • 원시 표현식과 조합: 수치와 산술 연산은 원시 데이터와 원시 함수에 해당합니다. 조합의 중첩은 연산들을 조합하는 수단을 제공합니다.
  • 상수 선언: 이름과 값을 연관시키는 상수 선언은 제한적이나마 추상화의 수단을 제공합니다.
  • 함수 선언: 복합 연산에 이름을 붙여서 그 연산을 하나의 단위로 지칭하게 하는 함수 선언은 상수 선언보다 훨씬 강력한 추상화 기법입니다.

제곱 함수 예시

제곱이라는 개념을 표현하는 문제로 시작해봅시다. 일상 언어에서는 제곱이라는 것을 뭔가를 제곱하려면 그것을 자기 자신과 곱하라고 표현할 수 있습니다. 이를 자바스크립트로 표현하면 다음과 같습니다.

function square(x) {
return x * x;
}

이것은 square라는 이름이 붙은 하나의 복합 함수입니다. 이 함수는 뭔가에 자기 자신을 곱하는 연산을 나타냅니다. 곱할 대상에는 x라는 지역 이름이 부여됩니다. 이 지역 이름은 일상 언어의 대명사와 같은 역할을 합니다. 해석기는 앞의 함수 선언을 해석해서 이 복합 함수에 square라는 이름을 연관시킵니다.

가장 단순한 형태의 함수 선언은 다음과 같습니다.

function 이름(매개변수들) {
return 표현식;
}

이름은 환경 안에서 함수 정의와 연관시킬 기호입니다. 매개변수들은 함수의 본문 안에서 함수의 인수들을 지칭하는 데 사용할 지역 이름들입니다. 이 매개변수들은 소괄호를 감싸고 각각을 쉼표로 구분합니다. 이는 함수를 인수들에 적용할 때의 구문과 동일합니다. 이 가장 간단한 형태의 함수 선언에서 함수의 본문은 반환문 하나로만 구성되며, 반환문은 키워드 return 다음에 반환 표현식이 오는 형태입니다. 반환 표현식은 함수 적용의 값을 산출합니다. 함수 호출 시 주어진 실제 인수들을 반환 표현식에 있는 매개변수들에 대입해서 평가한 결과가 곧 함수 적용의 값입니다. 상수 선언과 표현식 문장처럼 반환문은 세미콜론으로 끝납니다.

square라는 함수를 선언했으니, 이제 그것을 하나의 함수 적용 표현식에서 사용할 수 있습니다. 함수 적용은 표현식들로부터 더 큰 표현식을 만드는 또 다른 종류의 조합입니다. 함수 적용의 일반 형태는 다음과 같습니다:

함수-표현식(인수-표현식들)

여기서 함수-표현식은 쉼표로 분리된 인수-표현식들에 적용할 함수입니다. 함수 적용을 평가할 때 해석기는 연산자 조합의 평가 절차와 상당히 비슷한 절차를 따릅니다. 그 절차는 다음과 같습니다. 함수 적용을 평가하려면 다음을 수행합니다:

적용의 부분식들, 즉 함수 표현식과 인수 표현식들을 각각 평가합니다. 함수, 즉 함수 표현식의 값을 인수 표현식 값들에 적용합니다.

square(2 + 5); // 49 square(square(3)); // 81

이 예에서 보듯이, 함수 적용 표현식을 다른 함수 적용의 인수 표현식으로 사용하는 것도 당연히 가능합니다.

square를 다른 함수를 정의하는 구축 요소로 사용할 수도 있습니다. 다음은 x^2 + y^2를 나타낸 것입니다.

square(x) + square(y);

더 나아가서, sum_of_squares 자체를 또 다른 함수의 구축 요소로 사용할 수 있습니다.

function f(a) {
return sum_of_squares(a + 1, a * 2);
}
f(5); // 136

이런 복합 함수들 외에, 모든 자바스크립트 환경은 해석기 자체에 내장된, 또는 표준 라이브러리로부터 적재한 원시 함수들도 제공합니다. 이 책에 쓰이는 자바스크립트 환경에는 연산자의 형태로 제공되는 원시 함수들 외에도 math_log 같은 함수들이 있습니다. 이런 추가적인 원시 함수들을 사용하는 방법은 복합 함수를 사용하는 방법과 정확히 동일합니다. 예를 들어 math_log(1)의 적용을 평가한 결과는 수치 0입니다. 사실 앞에 나온 sum_of_squares의 정의만 보고는 square가 해석기에 내장된 함수인지, 라이브러리에서 적재한 함수인지, 아니면 하나의 복합 함수로서 정의된 것인지 구별할 수 없습니다.

요약 및 추가 정보

요약

  • 원시 표현식과 조합: 수치와 산술 연산은 원시 데이터와 원시 함수에 해당합니다. 조합의 중첩은 연산들을 조합하는 수단을 제공합니다.
  • 상수 선언: 이름과 값을 연관시키는 상수 선언은 제한적이나마 추상화의 수단을 제공합니다.
  • 함수 선언: 복합 연산에 이름을 붙여서 그 연산을 하나의 단위로 지칭하게 하는 함수 선언은 상수 선언보다 훨씬 강력한 추상화 기법입니다.

질문과 답변

질문 1: 상수 선언이 왜 중요한가요?

  • 답변: 상수 선언은 복합적인 연산 결과를 간단한 이름으로 지칭할 수 있게 해줍니다. 이는 코드의 가독성을 높이고, 유지보수성을 향상시키며, 오류를 줄이는 데 도움이 됩니다. 상수 선언을 통해 복잡한 계산적 객체의 세부 사항을 반복해서 명시할 필요 없이 간편하게 사용할 수 있습니다.

질문 2: 왜 const 키워드를 사용하나요?

  • 답변: const 키워드는 변하지 않는 값을 선언할 때 사용됩니다. 이를 통해 의도하지 않은 값의 변경을 방지하고, 코드의 안정성을 높일 수 있습니다. const로 선언된 변수는 재할당이 불가능하므로, 상수로서의 역할을 확실히 합니다.

질문 3: 상수 선언의 장점은 무엇인가요?

  • 답변: 상수 선언의 주요 장점은 다음과 같습니다:
    • 가독성 향상: 의미 있는 이름을 사용하여 코드를 더 쉽게 이해할 수 있습니다.
    • 유지보수 용이: 값을 한 번만 정의하면 여러 곳에서 재사용할 수 있어, 수정이 필요할 때 선언 부분만 변경하면 됩니다.
    • 오류 감소: 실수로 값을 변경하는 것을 방지하여, 버그 발생 가능성을 줄입니다.

질문 4: 환경(environment)이란 무엇인가요?

  • 답변: 환경은 이름과 값을 연관시킨 이름-객체 쌍을 저장하고 관리하는 메모리 공간을 의미합니다. 프로그래밍에서 변수나 상수의 이름과 그 값을 저장하고 필요할 때 조회할 수 있게 합니다. 여러 개의 환경이 존재할 수 있으며, 이는 변수의 스코프(scope)와 밀접한 관련이 있습니다.

질문 5: 어떻게 환경이 프로그래밍에 기여하나요?

  • 답변: 환경은 변수와 상수의 값을 저장하고 관리하여 프로그램이 올바르게 작동하도록 합니다. 변수의 스코프를 관리하고, 변수 충돌을 방지하며, 함수 호출 시 각기 다른 환경을 유지하여 상태를 관리합니다. 이는 프로그램의 구조화와 복잡한 연산 처리에 필수적입니다.

추가 설명

상수 선언과 함수 선언의 차이점: 상수 선언은 변하지 않는 값을 선언하는 데 사용되고, 함수 선언은 특정 작업을 수행하는 코드를 정의하는 데 사용됩니다. 예를 들어, 다음과 같은 함수 선언은 원의 면적을 계산하는 함수를 정의합니다:

function calculateArea(radius) {
const pi = 3.14159;
return pi * radius * radius;
}
const radius = 10;
const area = calculateArea(radius);
console.log(area); // 314.159

이 예제에서 calculateArea 함수는 주어진 반지름 값을 받아 원의 면적을 계산합니다. 함수 선언을 통해 코드를 재사용하고, 복잡한 계산을 단순화할 수 있습니다.

이러한 질문과 답변을 통해 상수 선언과 환경의 중요성 및 역할을 이해하고, 이를 실제 프로그래밍에 어떻게 적용할 수 있는지 배울 수 있습니다.